#include "XmlParser.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <sys/stat.h>

#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
#include <io.h>
#endif

using namespace std;

/**
 * define the functions here because we don't want to
 * export xmlNodePtr and other types in "CCUserDefault.h"
 */

xmlNodePtr XmlParser::getXMLNodeForKey(const char* pKey, xmlNodePtr parentNode, int index/* = 0*/)
{
	xmlNodePtr curNode = NULL;

	// check the key value
	if (! pKey)
	{
		return NULL;
	}

	do 
	{
		// find the node
		curNode = parentNode->xmlChildrenNode;
		int i = 0;
		while (NULL != curNode)
		{
			if (! xmlStrcmp(curNode->name, BAD_CAST pKey))
			{
				if (i == index)
				{
					break;
				}
				i++;
			}

			curNode = curNode->next;
		}
	} while (0);

	return curNode;
}

bool XmlParser::gotoNextGroup()
{
	if(NULL != mCurNode->next)
	{
		mCurNode = mCurNode->next;
		return true;
	}
	else
	{
		return false;
	}
}

bool XmlParser::gotoChildGroup()
{
	xmlNodePtr childNode = NULL;
	// find the node
	childNode = mCurNode->xmlChildrenNode;
	if (NULL != childNode)
	{
		mCurNode = childNode;
		return true;
	}
	else
	{
		return false;
	}
}

std::string XmlParser::curNodeName()
{
	if (mCurNode)
	{
		return (const char*)mCurNode->name;
	}
	return "";
}

std::vector<std::string> XmlParser::allPropertyNames()
{
	std::vector<std::string> allNames;
	if (mCurNode)
	{
		xmlAttrPtr attr = mCurNode->properties;
		while(attr)
		{
			allNames.push_back((const char*)attr->name);
			attr = attr->next;
		}
	}
	return allNames;
}

std::string XmlParser::getValueForKey(const char* pKey, int index/* = 0*/)
{
	const char* ret = NULL;
	std::string retStr;

	xmlNodePtr node = getXMLNodeForKey(pKey, mCurNode, index);

	// find the node
	if (node)
	{
		ret = (const char*)xmlNodeGetContent(node);
	}
	retStr = ret;
	xmlFree((void*)ret);
	// free doc

	return retStr;
}

void XmlParser::setValueForKey(const char* pKey, const char* pValue, int index/* = 0*/)
{
	xmlNodePtr node;

	// check the params
	if (! pKey || ! pValue)
	{
		return;
	}

	// find the node
	node = getXMLNodeForKey(pKey, mCurNode, index);

	// if node exist, change the content
	if (node)
	{
		xmlNodeSetContent(node, BAD_CAST pValue);
	}
	else
	{
		if (mCurNode)
		{
			// the node doesn't exist, add a new one
			// libxml in android donesn't support xmlNewTextChild, so use this approach
			xmlNodePtr tmpNode = xmlNewNode(NULL, BAD_CAST pKey);
			xmlNodePtr content = xmlNewText(BAD_CAST pValue);
			xmlAddChild(mCurNode, tmpNode);
			xmlAddChild(tmpNode, content);
		}	
	}
}

void XmlParser::saveXMLFile()
{
	// save file and free doc
	if (mDoc)
	{
		xmlSaveFormatFile(mFilename.c_str(),mDoc, 1);
	}
}

/**
 * If the user invoke delete CCUserDefault::sharedUserDefault(), should set m_spUserDefault
 * to null to avoid error when he invoke CCUserDefault::sharedUserDefault() later.
 */
XmlParser::~XmlParser()
{
	if (mDoc)
	{
		xmlFreeDoc(mDoc);
	}
}

XmlParser::XmlParser(const char* filename /*= 0*/):
	mDoc(0)
{
	initXMLFile(filename);
}

bool XmlParser::getBoolForKey(const char* pKey, int index/* = 0*/, bool defaultValue/* = false*/)
{
	std::string value = getValueForKey(pKey, index);
	bool ret = defaultValue;


	if (value == "true")
	{
		ret = true;
	}
	else if (value == "false")
	{
		ret = false;
	}

	return ret;
}

int XmlParser::getIntegerForKey(const char* pKey, int index/* = 0*/, int defautValue/* = 0*/)
{
	std::string value = getValueForKey(pKey, index);
	int ret = defautValue;

	if (value.empty() == false)
	{
		ret = atoi(value.c_str());
	}

	return ret;
}

std::vector<int> XmlParser::getIntArrayForKey(const char* pKey, int index /* = 0 */)
{
	std::vector<int> ret;

	std::string value = getStringForKey(pKey, index, "");
	
	int lastOffset = 0;
	int offset = value.find_first_of(',');
	do
	{
		std::string tmpStr = value.substr(lastOffset, offset-lastOffset);
		lastOffset = offset+1;
		offset = value.find_first_of(',', lastOffset);

		ret.push_back(atoi(tmpStr.c_str()));
	}while(lastOffset-1 != std::string::npos);

	return ret;
}

std::vector<float> XmlParser::getFloatArrayForKey(const char* pKey, int index /* = 0 */)
{
	std::vector<float> ret;

	std::string value = getStringForKey(pKey, index, "");

	int lastOffset = 0;
	int offset = value.find_first_of(',');
	while(offset != std::string::npos)
	{
		std::string tmpStr = value.substr(lastOffset, offset);
		lastOffset = offset;
		offset = value.find_first_of(',', lastOffset);

		ret.push_back(atof(tmpStr.c_str()));
	}

	return ret;
}

float XmlParser::getFloatForKey(const char* pKey, int index/* = 0*/, float defautValue/* = 0.0f*/)
{
	float ret = (float)getDoubleForKey(pKey, index, defautValue);
 
	return ret;
}

double XmlParser::getDoubleForKey(const char* pKey, int index/* = 0*/, double defautValue/* = 0.0*/)
{
	std::string value = getValueForKey(pKey, index);
	double ret = defautValue;

	if (value.empty() == false)
	{
		ret = atof(value.c_str());
	}

	return ret;
}

double XmlParser::getDoubleForAttr(const char* pKey, double defaultValue)
{
	std::string value = getAttribute(pKey);
	double ret = defaultValue;

	if (value.empty() == false)
	{
		ret = atof(value.c_str());
	}
	return ret;
}

float XmlParser::getFloatForAttr(const char* pKey, float defaultValue/* =0.0 */)
{
	return getDoubleForAttr(pKey, defaultValue);
}

cocos2d::CCRect XmlParser::getRectForAttr(const char* pKey, const cocos2d::CCRect& defaultRect/* =cocos2d::CCRect */)
{
	std::string value = getAttribute(pKey);

	if (value.empty())
	{
		return defaultRect;
	}
	return convertToRect(value);
}

cocos2d::CCSize XmlParser::getSizeForAttr(const char* pKey, const cocos2d::CCSize& defaultSize/* =cocos2d::CCSize */)
{
	std::string sizeStr = getAttribute(pKey);
	if (sizeStr.empty())
	{
		return defaultSize;
	}

	cocos2d::CCPoint pt = convertToPoint(sizeStr);
	return cocos2d::CCSize(pt.x, pt.y);
}

cocos2d::ccColor4B XmlParser::getColorForAttr(const char* pKey, const cocos2d::ccColor4B& defaultColor/* =cocos2d::ccc4 */)
{
	std::string clrStr = getAttribute(pKey);
	if (clrStr.empty())
	{
		return defaultColor;
	}
	cocos2d::CCRect rtClr = convertToRect(clrStr);

	return cocos2d::ccc4(rtClr.origin.x, rtClr.origin.y, rtClr.size.width, rtClr.size.height);
}

#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
// create new xml file
bool XmlParser::createXMLFile(const char* newFileName)
{ 
	bool bRet = false;
    xmlDocPtr doc = NULL;
    
	do 
	{
		// new doc
		doc = xmlNewDoc(BAD_CAST"1.0");
		if (doc == NULL)
		{
			CCLOG("can not create xml doc");
			break;
		}
        
		// new root node
		xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "Root");
		if (rootNode == NULL)
		{
			CCLOG("can not create root node");
			break;
		}
        
		// set root node
		xmlDocSetRootElement(doc, rootNode);

		xmlSaveFile(newFileName, doc);
        
		bRet = true;
	} while (0);
    
	// if doc is not null, free it
	if (doc)
	{
		xmlFreeDoc(doc);
	}
    
	return bRet;
}

bool XmlParser::newGroup(const char* groupName)
{
	xmlNodePtr pNewNode = xmlNewChild(mCurNode, NULL, BAD_CAST groupName, NULL);
	if (pNewNode == NULL)
	{
		CCLOG("XmlParser::newGroup xmlNewChild error !");
		return false;
	}
	mCurNode = pNewNode;
	return true;
}
#else
// create new xml file
bool XmlParser::createXMLFile(const char* newFileName)
{ 
	return true;
}

bool XmlParser::newGroup(const char* groupName)
{
	return true;
}
#endif

void XmlParser::setAtrribute(const char* pKey, const char* value)
{
	xmlAttrPtr attr = xmlSetProp(mCurNode, BAD_CAST pKey, BAD_CAST value);
	if (attr == NULL)
	{
		CCLOG("XmlParser::setAtrribute xmlSetProp error !");
	}
}

std::string XmlParser::getAttribute(const char* pKey)
{
	xmlChar* attr = xmlGetProp(mCurNode, BAD_CAST pKey);
	if (attr == NULL)
	{
		CCLOG("XmlParser::getAttribute : %s error !", pKey);
		return "";
	}

	std::string ret((char*)(attr));
	xmlFree((void*)attr);
	return ret;
}

int XmlParser::getIntegerForAttr(const char* pKey, int defaultValue /* = 0 */)
{
	std::string ret = getAttribute(pKey);
	if (ret.empty())
	{
		return defaultValue;
	}
	return atoi(ret.c_str());
}

string XmlParser::getStringForKey(const char* pKey, int index/* = 0*/, const char* defaultValue /*= ""*/)
{
	std::string value = getValueForKey(pKey, index);
	string ret(defaultValue);

	if (value.empty() == false)
	{
		ret = string(value);
	}

	return ret;
}

void XmlParser::setBoolForKey(const char* pKey, bool value, int index/* = 0*/)
{
    // save bool value as sring

	if (true == value)
	{
		setStringForKey(pKey, "true", index);
	}
	else
	{
		setStringForKey(pKey, "false", index);
	}
}

void XmlParser::setIntegerForKey(const char* pKey, int value, int index/* = 0*/)
{
	// check key
	if (! pKey)
	{
		return;
	}

	// format the value
	char tmp[50];
	memset(tmp, 0, 50);
	sprintf(tmp, "%d", value);

	setValueForKey(pKey, tmp, index);
}

void XmlParser::setFloatForKey(const char* pKey, float value, int index/* = 0*/)
{
	setDoubleForKey(pKey, value, index);
}

void XmlParser::setDoubleForKey(const char* pKey, double value, int index/* = 0*/)
{
	// check key
	if (! pKey)
	{
		return;
	}

	// format the value
    char tmp[50];
	memset(tmp, 0, 50);
	sprintf(tmp, "%f", value);

	setValueForKey(pKey, tmp, index);
}

void XmlParser::setStringForKey(const char* pKey, std::string value, int index/* = 0*/)
{
	// check key
	if (! pKey)
	{
		return;
	}

	setValueForKey(pKey, value.c_str(), index);
}

bool XmlParser::isXMLFileExist(const char* filename)
{
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
    unsigned long nSize = 0;
    unsigned char* pBuf = cocos2d::CCFileUtils::sharedFileUtils()->getFileData(filename, "r", &nSize);
    if (nSize == 0 || pBuf == NULL) {
        return false;
    }
    return true;
#endif
	bool bRet = false;
	struct stat buf;

	bRet = access(filename, 0) == 0;

	if (bRet) 
	{
		int cc;

		cc = stat(filename, &buf);
		if (cc == 0 && (buf.st_mode & S_IFDIR) || cc == -1) 
		{
			bRet = false;
		}
		else 
		{
			bRet = true;
		}
	}

	return bRet;
}

bool XmlParser::initXMLFile(const char* filename)
{
	if (filename)
	{
		unsigned long nSize  = 0;
		unsigned char* pBuffer = NULL;
		pBuffer = cocos2d::CCFileUtils::sharedFileUtils()->getFileData(filename, "rb", &nSize);
		mDoc = xmlReadMemory((char*)pBuffer, nSize, filename, "utf-8", XML_PARSE_RECOVER);
		CC_SAFE_DELETE_ARRAY(pBuffer);

		if (mDoc == 0)
		{
			CCLOG("%s file error! ", filename);
			return false;
		}
		mCurNode = xmlDocGetRootElement(mDoc);
		if (mCurNode == 0)
		{
			mCurNode = xmlNewNode(NULL, BAD_CAST "Root");
			xmlDocSetRootElement(mDoc, mCurNode);
		}
		if (mDoc == 0)
		{
			CCLOG("%s file has't root node! ", filename);
			return false;
		}
		mFilename = filename;
		return true;
	}
	else
	{
		CCLOG("%s file is not exist! ", filename);
		return false;
	}
}

void XmlParser::reset()
{
	xmlNodePtr rootNode = xmlDocGetRootElement(mDoc);
	xmlUnlinkNode(rootNode);
	xmlFreeNode(rootNode);
	mCurNode = xmlNewNode(NULL, BAD_CAST "Root");
	xmlDocSetRootElement(mDoc, mCurNode);
}

bool XmlParser::beginGroup(const char* groupName, int index/* = 0*/)
{
	xmlNodePtr tempNode = getXMLNodeForKey(groupName, mCurNode, index);
	
	if (tempNode == 0)
	{
		CCLOG("xml file has not %d node", groupName);
		return false;
	}

	mCurNode = tempNode;

	return true;
}

void XmlParser::endGroup()
{
	if (mCurNode->parent == 0)
	{
		CCLOG("xml node is root node !");
		return;
	}
	mCurNode = mCurNode->parent;
}

cocos2d::CCRect XmlParser::getRectForKey(const char* pKey, int index/* = 0*/, const cocos2d::CCRect& defaultRect/* =cocos2d::CCRect */)
{
	std::string rectStr = getStringForKey(pKey, index);
	if (rectStr.empty())
	{
		return defaultRect;
	}

	return convertToRect(rectStr);
}

cocos2d::CCPoint XmlParser::getPointForAttr(const char* pKey, const cocos2d::CCPoint& defaultPoint)
{
	std::string ptStr = getAttribute(pKey);
	if (ptStr.empty())
	{
		return defaultPoint;
	}

	return convertToPoint(ptStr);
}

cocos2d::CCPoint XmlParser::getPointForKey(const char* pKey, int index/* = 0*/, const cocos2d::CCPoint& defaultPoint/* =cocos2d::CCPoint */)
{
	std::string ptStr = getStringForKey(pKey, index);
	if (ptStr.empty())
	{
		return defaultPoint;
	}

	return convertToPoint(ptStr);
}

cocos2d::CCSize XmlParser::getSizeForKey(const char* pKey, int index/* = 0*/, const cocos2d::CCSize& defaultSize/* =cocos2d::CCSize */)
{
	std::string sizeStr = getStringForKey(pKey, index);
	if (sizeStr.empty())
	{
		return defaultSize;
	}

	cocos2d::CCPoint pt = convertToPoint(sizeStr);
	return cocos2d::CCSize(pt.x, pt.y);
}

cocos2d::CCRect convertToRect(const std::string& str)
{
	cocos2d::CCRect rect(0,0,0,0);

	int lastOffset = 0;
	int offset = str.find(',', lastOffset);
	if (std::string::npos != offset)
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		rect.origin.x = atof(strTemp.c_str());
	}

	lastOffset = offset+1;
	offset = str.find(',', lastOffset);
	if (std::string::npos != offset)
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		rect.origin.y = atof(strTemp.c_str());
	}

	lastOffset = offset+1;
	offset = str.find(',', lastOffset);
	if (std::string::npos != offset)
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		rect.size.width = atof(strTemp.c_str());
	}

	lastOffset = offset+1;
	offset = std::string::npos;
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		rect.size.height = atof(strTemp.c_str());
	}
	return rect;
}

cocos2d::CCPoint convertToPoint(const std::string& str)
{
	cocos2d::CCPoint pt(0,0);

	int lastOffset = 0;
	int offset = str.find(',', lastOffset);
	if (std::string::npos != offset)
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		pt.x = atof(strTemp.c_str());
	}

	lastOffset = offset+1;
	offset = std::string::npos;
	{
		std::string strTemp = str.substr(lastOffset, offset-lastOffset);
		pt.y = atof(strTemp.c_str());
	}

	return pt;
}